/***************************************************************************
 *   Copyright (C) 2008 by Chris Bornholdt,,,   *
 *   chris.bornholdt@gmail.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
 
#ifndef _OBJECT_AREA_HPP_
#define _OBJECT_AREA_HPP_

#include <boost/concept_check.hpp>
#include <tr1/memory>
#include <cmath>
#include <cassert>
#include "strideiterator.hpp"

using std::tr1::shared_ptr;
 
template<typename Element, template<typename _Element, 
	typename _Alloc> class Storage, typename Alloc = std::allocator<Element> >
class object_area
{
	BOOST_CONCEPT_ASSERT((boost::Mutable_RandomAccessContainer<Storage<Element, Alloc> >));

	public:
		typedef Element value_type;
		typedef Storage<Element, Alloc> storage_type;
		
		object_area() : storage_(), width_(0), height_(0) {}
		object_area(const object_area& other) : storage_(other.storage_), width_(other.width_), 
			height_(other.height_){}
		object_area(const storage_type& other) : storage_(other)
		{
			width_ = std::sqrt(other.size());
			while(other.size() % width_) width_--;
			height_ = other.size() / width_;
		}
		object_area(std::size_t width, std::size_t height) : storage_(width * height), 
			width_(width), height_(height){}
			
		const object_area& operator=(const object_area& other)
 		{
 			if(this != &other)
 			{
 				storage_ = other.storage_;
 				width_ = other.width_;
 				height_ = other.height_;
 			}
 			
 			return *this;
 		}
		
 		std::size_t width() const	{	return width_;	}
 		std::size_t height() const	{	return height_;	}
 		std::size_t size() const	{	return width_ * height_;	}
 		
 		void resize(std::size_t size)	{	storage_.resize(size);	}
 		void resize(std::size_t width, std::size_t height)
 		{
 			storage_.resize(width * height);
 			width_ = width;
 			height_ = height;
 		}
 		
 		typedef typename storage_type::iterator iterator;
 		typedef typename storage_type::const_iterator const_iterator;
 		
 		iterator begin()				{	return storage_.begin();	}
 		const_iterator begin() const		{	return storage_.begin();	}
 		iterator end()				{	return storage_.end();		}
 		const_iterator end() const		{	return storage_.end();		}
 		
 		typedef typename storage_type::iterator strip_iterator;
 		typedef typename storage_type::const_iterator const_strip_iterator;
 		typedef std::list<std::pair<strip_iterator, strip_iterator> > strip_list;
 		typedef std::list<std::pair<const_strip_iterator, const_strip_iterator> >
 			const_strip_list;
 			
 		shared_ptr<strip_list> strips()
 		{
 			shared_ptr<strip_list> return_value(new strip_list(height_));
 			
 			strip_iterator iter = storage_.begin();
 			typename strip_list::iterator strip_iter = return_value->begin();
 			for(;strip_iter != return_value->end(); iter += width_, ++strip_iter)
 				*strip_iter = std::make_pair(iter, iter + width_);
 				
 			return return_value;
 		}
 		
 		shared_ptr<const_strip_list> strips() const
 		{
 			shared_ptr<const_strip_list> return_value(new const_strip_list(height_));
 			
 			const_strip_iterator iter = storage_.begin();
 			typename const_strip_list::iterator strip_iter = return_value->begin();
 			for(;strip_iter != return_value->end(); iter += width_, ++strip_iter)
 				*strip_iter = std::make_pair(iter, iter + width_);
 				
 			return return_value;
 		}
 		
 		typedef stride_iterator<strip_iterator> vstrip_iterator;
 		typedef stride_iterator<const_strip_iterator> const_vstrip_iterator;
 		typedef std::list<std::pair<vstrip_iterator, vstrip_iterator> > vstrip_list;
 		typedef std::list<std::pair<const_vstrip_iterator, const_vstrip_iterator> >
 			const_vstrip_list;
 			
 		shared_ptr<vstrip_list> vstrips()
 		{
 			shared_ptr<vstrip_list> return_value(new vstrip_list(width_));
 			
 			strip_iterator iter = storage_.begin(), end_iter = storage_.end();
 			typename vstrip_list::iterator strip_iter = return_value->begin();
 			for(;strip_iter != return_value->end(); ++iter, ++end_iter, ++strip_iter)
 				*strip_iter = std::make_pair(vstrip_iterator(iter, width_), 
 					vstrip_iterator(end_iter, width_));
 				
 			return return_value;
 		}
 		
 		shared_ptr<const_vstrip_list> vstrips() const
 		{
 			shared_ptr<const_vstrip_list> return_value(new const_vstrip_list(width_));
 			
 			const_strip_iterator iter = storage_.begin(), end_iter = storage_.end();
 			typename const_vstrip_list::iterator strip_iter = return_value->begin();
 			for(;strip_iter != return_value->end(); ++iter, ++end_iter, ++strip_iter)
 				*strip_iter = std::make_pair(const_vstrip_iterator(iter, width_), 
 					const_vstrip_iterator(end_iter, width_));
 				
 			return return_value;
 		}	
 		
 		shared_ptr<object_area> sub_area(unsigned int x, unsigned int y, std::size_t width, 
 			std::size_t height) const
 		{
 			assert(x + width <= width_);
 			assert(y + height <= height_);
 			
 			shared_ptr<object_area> return_area(new object_area(width, height));
 			typename storage_type::const_iterator source = storage_.begin() + x + y * width_;
 			typename storage_type::iterator destination = return_area->storage_.begin();
 			for(unsigned int i = 0; i < height; i++, source += width_, 
 					destination += return_area->width_)
 				std::copy(source, source + width, destination);
 			return return_area;
 		}
 				
 		void blit_area(const object_area& other, unsigned int x = 0, unsigned int y = 0)
 		{
 			assert(x + other.width_ <= width_);
 			assert(y + other.height_ <= height_);
 			
 			typename storage_type::iterator dest = storage_.begin() + x + y * width_;
 			typename storage_type::const_iterator source = other.storage_.begin();
 			for(unsigned int i = 0; i < other.height_; i++, dest += width_, 
 				source += other.width_)
 				std::copy(source, source + other.width_, dest);
 		}
 		
 		void blit_sub_area(const object_area& other, unsigned int x, unsigned int y, 
 			unsigned int width, unsigned int height, unsigned int x2 = 0, unsigned int y2 = 0)
 		{
 			assert(x + width <= width_ && x2 + width <= other.width_);
 			assert(y + height <= height_ && y2 + height <= other.height_);
 			
 			typename storage_type::iterator dest = storage_.begin() + x + y * width_;
 			typename storage_type::const_iterator source = other.storage_.begin() + 
 				x2 + y2 * other.row_stride_;
 			for(unsigned int i = 0; i < height; i++, dest += width_, 
 				source += other.width_)
 				std::copy(source, source + width, dest);
 		}
 		
 		Element& operator()(unsigned int x, unsigned int y)
 		{	
 			assert(x < width_);
 			assert(y < height_);
 			return storage_[x + y * width_];	
 		}
 		const Element& operator()(unsigned int x, unsigned int y) const
 		{
 			assert(x < width_);
 			assert(y < height_);
 			return storage_[x + y * width_];	
 		}
 		
 		Element& operator[](int i)
 		{
 			assert(0 <= i && i < width_ * height_);
 			return storage_[i];
 		}
 		const Element& operator[](int i) const
 		{
 			assert(0 <= i && i < width_ * height_);
 			return storage_[i];
 		}
 		
 		
 		
	private:
		storage_type storage_;
		std::size_t width_, height_;
};
 
#endif
